#include "http.h"
#include "epoll_loop.h"
#include "file.h"
#include "socket.h"
#include <algorithm>
#include <cctype>
#include <cerrno>
#include <cstring>
#include <iostream>
#include <netinet/in.h>
#include <optional>
#include <sys/socket.h>
#include <utility>

#include <chrono>
using namespace std::chrono_literals;

namespace cobt {
HttpRequest::HttpRequest() {}
HttpRequest::HttpRequest(std::string &&path, std::string &&method,
                         std::unordered_map<std::string, std::string> &&headers,
                         std::string &&body = "") {
  m_path = std::move(path);
  m_method = std::move(method);
  std::transform(m_method.begin(), m_method.end(), m_method.begin(), toupper);
  m_body = std::move(body);
  for (auto &[k, v] : std::move(headers)) {
    std::string key(k);
    std::transform(k.begin(), k.end(), key.begin(), tolower);
    m_headers.insert({key, std::move(v)});
  }
  if (body.size()) {
    m_headers["content-length"] = std::to_string(m_body.size());
  }
}
Task<bool> HttpRequest::write_into(FileStream &fs) {
  if (!co_await fs.puts(m_method + " ")) {
    co_return false;
  }
  if (!co_await fs.puts(m_path + " ")) {
    co_return false;
  }
  if (!co_await fs.puts("HTTP/1.1\r\n")) {
    co_return false;
  }
  for (auto &[k, v] : m_headers) {
    if (!co_await fs.puts(k + ": ")) {
      co_return false;
    }
    if (!co_await fs.puts(v + "\r\n")) {
      co_return false;
    }
  }

  if (!co_await fs.puts("\r\n")) {
    co_return false;
  }
  if (!co_await fs.puts(m_body)) {
    co_return false;
  }
  co_return co_await fs.flush();
}

Task<bool> HttpResponse::read_from(FileStream &fs) {
  auto line = co_await fs.getline();
  auto pos = line.find(' ');
  if (pos == std::string::npos || pos + 1 == line.size()) {
    co_return false;
  }
  auto status = line.find(' ', ++pos);
  if (status == std::string::npos) {
    co_return false;
  }
  try {
    m_status = std::stoi(line.substr(pos, status - pos));
  } catch (const std::exception &e) {
    co_return false;
  }
  std::cout << m_status << std::endl;
  while (true) {
    line = co_await fs.getline();
    if (line == "\r") {
      break;
    }
    pos = line.find(':');
    if (pos == std::string::npos || pos == line.size() - 1) {
      co_return false;
    }
    std::string key = std::move(line.substr(0, pos));
    std::transform(key.begin(), key.end(), key.begin(), tolower);
    if (++pos >= line.size() || line[pos] != ' ') {
      co_return false;
    }
    auto value = line.substr(++pos);
    m_headers[key] = std::move(value);
  }
  auto it = m_headers.find("content-length");
  if (it == m_headers.end()) {
    co_return false;
  }

  int content_length;
  try {
    content_length = std::stoi(it->second);
  } catch (std::exception &e) {
    co_return false;
  }
  m_body = co_await fs.getn(content_length);
  if (m_body.size() != content_length) {
    co_return false;
  }
  co_return true;
}
std::string HttpResponse::get_body() const { return m_body; }

HttpClient::HttpClient(const std::string &host, uint16_t port)
    : m_host(host), m_port(port) {}

Task<std::optional<HttpResponse>, HttpPromise>
HttpClient::get(const std::string &path) {
  auto coro = (co_await cobt::GetCoroutine<HttpPromise>());
  SocketAddress addr(m_host, m_port);
  int fd;
  if (addr.get_family() == AF_INET6) {
    fd = socket(AF_INET6, SOCK_STREAM, 0);
  } else {
    fd = socket(AF_INET, SOCK_STREAM, 0);
  }
  int flags = fcntl(fd, F_GETFL, 0);
  fcntl(fd, F_SETFL, flags | O_NONBLOCK);
  FileHandle conn(fd);
  if (co_await cobt::connect_async(conn.get_fd(), addr, 5s) < 0) {
    std::cout << "connect failed" << std::endl;
    co_return std::nullopt;
  }

  std::cout << "connect success" << std::endl;
  cobt::FileStream fs(std::move(conn), 10s);
  cobt::HttpRequest req("/", "GET", {{"host", m_host}}, "");
  if (!co_await req.write_into(fs)) {
    std::cout << "write failed" << std::endl;
    co_return std::nullopt;
  }
  cobt::HttpResponse resp;
  if (!co_await resp.read_from(fs)) {
    std::cout << "read failed:" << resp.get_body() << std::endl;
    co_return std::nullopt;
  }
  co_return resp;
}

} // namespace cobt
